1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.hipaudio.audioclip;
12 import hip.util.path : baseName;
13 import hip.error.handler;
14 import hip.audio_decoding.audio;
15 import hip.hipaudio.audio;
16 import hip.hipaudio.audiosource;
17 public import hip.api.audio.audioclip;
18 
19 
20 union HipAudioBuffer
21 {
22     import hip.hipaudio.config;
23     static if(HasOpenAL)
24     {
25         import bindbc.openal;
26         ALuint al;
27     }
28     static if(HasOpenSLES)
29     {
30         import opensles.sles;
31         import hip.hipaudio.backend.sles;
32         SLIBuffer* sles;
33     }
34     static if(HasXAudio2)
35     {
36         import directx.xaudio2;
37         XAUDIO2_BUFFER* xaudio;
38     }
39     static if(HasWebAudio)
40     {
41         import hip.hipaudio.backend.webaudio.clip;
42         size_t webaudio;
43     }
44     static if(HasAVAudioEngine)
45     {
46         import hip.hipaudio.backend.avaudio.clip;
47         AVAudioPCMBuffer avaudio;
48     }
49 }
50 
51 struct HipAudioBufferWrapper
52 {
53     HipAudioBuffer buffer;
54     bool isAvailable;
55 }
56 
57 
58 
59 /** 
60 * Wraps a decoder onto it. Basically an easier interface with some more controls
61 *  that would be needed inside specific APIs.
62 *
63 *   AudioClip flow basically consists in: 
64 *   1. Initialize the audio clip with the current decoder.
65 *   2. Call `.load`, which calls `.decode`
66 *   3. HipAudioSource calls `.setClip`, which should call `clip.getBuffer`, which gets the buffer
67 *   wrapped by the current implementation `createBuffer`, and then the buffer is enqueued.
68 */
69 public abstract class HipAudioClip : IHipAudioClip
70 {
71     IHipAudioDecoder decoder;
72     ///Unused for non streamed. It is the binary loaded from a file which will be decoded
73     ubyte[] dataToDecode;
74     ///Unused for non streamed. Where the user will get its audio decoded.
75     ubyte[] outBuffer;
76     ///Unused for non streamed
77     uint chunkSize;
78 
79 
80     HipAudioClipHint hint;
81 
82     /**
83     *   Buffers recycled from HipAudioSource.
84     *
85     *   When source notifies that the buffer is free, it is added to
86     *   that array. When getBuffer is called, it could send one
87     *   of those recycleds.
88     */ 
89     private HipAudioBufferWrapper[] buffersToRecycle;
90     private HipAudioBufferWrapper[] buffersCreated;
91     
92     size_t totalDecoded = 0;
93 
94     HipAudioType type;
95     HipAudioEncoding encoding;
96     bool isStreamed = false;
97     string fileName;
98 
99     
100     ///Event method called when the stream is updated
101     protected abstract void  onUpdateStream(ubyte[] data, uint decodedSize);
102     /**
103     *   Always alocates a pointer to the buffer data. So, after getting its content. Send it to the
104     *   recyclable buffers
105     */
106     protected abstract HipAudioBufferWrapper createBuffer(ubyte[] data);
107     protected abstract void  destroyBuffer(HipAudioBuffer* buffer);
108     
109     /** The buffer is actually any kind of external API buffer, it is the buffer contained in
110     *   HipAudioBufferWrapper.
111     *
112     *   OpenAL: `int` containing the buffer ID
113     *   OpenSL ES: `SLIBuffer`
114     *   XAudio2: To be thought?
115     */
116     public    abstract void  setBufferData(HipAudioBuffer* buffer, ubyte[] data, uint size);
117 
118     final immutable(HipAudioClipHint)* getHint(){return cast(immutable)&hint;}
119 
120     this(IHipAudioDecoder decoder, HipAudioClipHint hint){this.decoder = decoder; this.hint = hint;}
121     this(IHipAudioDecoder decoder, HipAudioClipHint hint, uint chunkSize)
122     in(chunkSize > 0, "Chunk must be greater than 0")
123     {
124         this(decoder, hint);
125         this.chunkSize = chunkSize;
126         outBuffer = new ubyte[chunkSize];
127         ErrorHandler.assertExit(outBuffer != null, "Out of memory");
128     }
129     /**
130     *   Should implement the specific loading here
131     */
132     public bool loadFromMemory(in ubyte[] data, HipAudioEncoding encoding, HipAudioType type,
133     void delegate(in ubyte[]) onSuccess, void delegate() onFailure)
134     {
135         this.type = type;
136         this.isStreamed = false;
137         return decoder.loadData(data, encoding, type, hint, onSuccess, onFailure);
138     }
139     /**
140     *   Decodes a bit more of the current buffer
141     */
142     public final uint updateStream()
143     {
144         ErrorHandler.assertExit(chunkSize > 0, "Can't update stream with 0 sized buffer.");
145         uint dec = decoder.updateDecoding(outBuffer);
146         totalDecoded+= dec;
147         onUpdateStream(outBuffer, dec);
148         return dec;
149     }
150     package final HipAudioBufferWrapper* findBuffer(HipAudioBuffer buf)
151     {
152         foreach(ref b; buffersCreated)
153             if(b.buffer == buf)
154                 return &b;
155         return null;
156     }
157 
158     /**
159     *   Attempts to get a buffer from the buffer recycler. 
160     *   Used for when loadStreamed must set a buffer available
161     */
162     public    final    HipAudioBuffer pollFreeBuffer()
163     {
164         if(buffersToRecycle.length > 0)
165         {
166             HipAudioBufferWrapper* w = &(buffersToRecycle[$ - 1]);
167             buffersToRecycle.length--;
168             w.isAvailable = false;
169             return w.buffer;
170         }
171         return HipAudioBuffer.init;
172     }
173     
174     public final HipAudioBuffer getBuffer(ubyte[] data, uint size)
175     {
176         HipAudioBuffer ret;
177         if((ret = pollFreeBuffer()) != HipAudioBuffer.init)
178         {
179             setBufferData(&ret, data, size);
180             return ret;
181         }
182         HipAudioBufferWrapper w = createBuffer(data);
183         setBufferData(&w.buffer, data, size);
184         ret = w.buffer;
185         buffersCreated~=w;
186         return ret;
187     }
188     HipAudioBufferAPI* _getBufferAPI(ubyte[] data, uint size)
189     {
190         HipAudioBuffer* temp = new HipAudioBuffer();
191         *temp = getBuffer(data, size);
192         return cast(HipAudioBufferAPI*)temp;
193     }
194     IHipAudioClip getAudioClipBackend(){return this;}
195 
196     package final void setBufferAvailable(HipAudioBuffer buffer)
197     {
198         HipAudioBufferWrapper* w = findBuffer(buffer);
199         ErrorHandler.assertExit(w != null, "AudioClip Error: No buffer was found when trying to set it available");
200         buffersToRecycle~= *w;
201         w.isAvailable = true;
202     }
203 
204     /**
205     *   Saves which data should be decoded and do 1 decoding frame
206     */
207     public uint loadStreamed(in ubyte[] data, HipAudioEncoding encoding)
208     {
209         dataToDecode = cast(ubyte[])data;
210         this.encoding = encoding;
211         ErrorHandler.assertExit(chunkSize > 0, "Can't update stream with 0 sized buffer.");
212         uint dec = decoder.startDecoding(dataToDecode, outBuffer, chunkSize, encoding);
213         totalDecoded+= dec;
214         onUpdateStream(outBuffer, dec);
215         return dec;
216     }
217 
218     ///Returns the streambuffer if streamed, else, returns entire sound
219     public ubyte[] getClipData()
220     {
221         if(isStreamed)
222             return outBuffer;
223         return decoder.getClipData();
224     }
225     ///Returns how much has been decoded
226     public size_t getClipSize()
227     {
228         if(isStreamed)
229             return totalDecoded;
230         return decoder.getClipSize();
231     }
232     public float getDuration(){return decoder.getDuration();}
233     public final uint getSampleRate(){return decoder.getSamplerate();}
234     public final float getDecodedDuration()
235     {
236         AudioConfig cfg = decoder.getAudioConfig();
237         import hip.console.log;
238         rawlog(cfg.getBitDepth, cfg.channels, cfg.sampleRate);
239         return getClipSize() / (cast(float) cfg.sampleRate);
240     }
241   
242 
243     public void unload()
244     {
245         decoder.dispose();
246         foreach (ref b; buffersCreated)
247             destroyBuffer(&b.buffer);
248         buffersCreated.length = 0;
249         if(outBuffer != null)
250         {
251             destroy(outBuffer);
252             outBuffer = null;
253         }
254     }
255 }